#include "QuadraticField.h"
#include "NumberTheory.h"
#include <sstream>


const QuadraticField QuadraticField::Zero = QuadraticField(0);
const QuadraticField QuadraticField::One = QuadraticField();

QuadraticField::QuadraticField(const Rational &a, const Rational &b, const Integer &d, bool ok)
{
	if (d == 1)
	{
		this->a = a + b;
		this->b = 0;
		this->d = 1;
	}
	else
	{
		this->a = a;

		if (ok)
		{
			this->b = b;
			this->d = d;
		}
		else
		{
			const auto p = NumberTheory::Factor(d);
			this->d = 1;
			Integer k;

			for (const auto &x: p)
			{
				k *= std::Exp(x.first, x.second >> 1);

				if (std::IsOdd(x.second))
					this->d *= x.first;
			}

			this->b = b * k;
		}
	}
}

const Rational& QuadraticField::Re(const QuadraticField &n) const
{
	return this->a;
}

const Rational& QuadraticField::Im(const QuadraticField &n) const
{
	return this->b;
}

const Integer& QuadraticField::Base(const QuadraticField &n) const
{
	return this->d;
}

const Rational Trace(const QuadraticField &n)
{
	return n.a * 2;
}

const Rational Norm(const QuadraticField &n)
{
	return std::Exp(n.a, 2) - n.d * std::Exp(n.b, 2);
}

const Integer QuadraticField::Radix(const Integer &d)
{
	if (std::sgn(d) == -1)
		return (_unsigned(d) & 3) == 3 ? d : d << 2;
	else
		return (_unsigned(d) & 3) == 1 ? d : d << 2;
}

const QuadraticField QuadraticField::Unit(const Integer &d)
{
	if (d == -1)
		return QuadraticField(0, 1, d, true);
	else if (d == -2 || d <= -5)
		return QuadraticField(-1, 0, d, true);
	else if (d == -3)
		return QuadraticField(Rational(1, 2, true), Rational(1, 2, true), d, true);
	else
		throw "TODO";
}

const std::string QuadraticField::str() const
{
	std::ostringstream str(this->a.str());

	if (this->d != 1)
	{
		if (this->b < 0)
			str << " - ";
		else
			str << " + ";

		str << std::abs(this->b).str() << " * sqrt(" << d.str() << ")";
	}

	return str.str();
}

std::ostream& operator<<(std::ostream &out, const QuadraticField &n)
{
	return out << n.str();
}

bool IsSameSystem(const QuadraticField &x, const QuadraticField &y)
{
	return x.d == 1 || y.d == 1 || x.d == y.d;
}

bool QuadraticField::operator==(const QuadraticField &n) const
{
	return this->a == n.a && this->b == n.b && IsSameSystem(*this, n);
}

bool QuadraticField::operator!=(const QuadraticField &n) const
{
	return !(*this == n);
}

const QuadraticField QuadraticField::Conjugate(const QuadraticField &n) const
{
	return QuadraticField(this->a, -this->b, this->d, true);
}

const QuadraticField QuadraticField::operator-() const
{
	return QuadraticField(-this->a, -this->b, this->d, true);
}

const QuadraticField Inverse(const QuadraticField &n)
{
	if (n == QuadraticField::Zero)
		throw;

	const Rational k = n.a * n.a - n.b * n.b * n.d;
	return QuadraticField(n.a / k, -n.b / k, n.d, true);
}

const QuadraticField QuadraticField::operator+(const QuadraticField &addend) const
{
	if (!IsSameSystem(*this, addend))
		throw;

	if (this->d == 1)
	{
		if (addend.d == 1)
			return QuadraticField(this->a + addend.a);
		else
			return QuadraticField(this->a + addend.a, addend.b, addend.d, true);
	}
	else
		return QuadraticField(this->a + addend.a, this->b + addend.b, this->d, true);
}

QuadraticField& QuadraticField::operator+=(const QuadraticField &addend)
{
	if (!IsSameSystem(*this, addend))
		throw;

	if (addend.d == 1)
	{
		this->a += addend.a;
		return *this;
	}
	else if (this->d == 1)
		return *this = QuadraticField(this->a + addend.a, addend.b, addend.d, true);
	else
	{
		this->a += addend.a;
		this->b += addend.b;
		return *this;
	}
}

const QuadraticField QuadraticField::operator-(const QuadraticField &subtrahend) const
{
	if (!IsSameSystem(*this, subtrahend))
		throw;

	if (this->d == 1)
	{
		if (subtrahend.d == 1)
			return QuadraticField(this->a - subtrahend.a);
		else
			return QuadraticField(this->a - subtrahend.a, subtrahend.b, subtrahend.d, true);
	}
	else
		return QuadraticField(this->a - subtrahend.a, this->b - subtrahend.b, this->d, true);
}

QuadraticField& QuadraticField::operator-=(const QuadraticField &subtrahend)
{
	if (!IsSameSystem(*this, subtrahend))
		throw;

	if (subtrahend.d == 1)
	{
		this->a -= subtrahend.a;
		return *this;
	}
	else if (this->d == 1)
		return *this = QuadraticField(this->a - subtrahend.a, subtrahend.b, subtrahend.d, true);
	else
	{
		this->a -= subtrahend.a;
		this->b -= subtrahend.b;
		return *this;
	}
}

const QuadraticField QuadraticField::operator*(const QuadraticField &factor) const
{
	if (!IsSameSystem(*this, factor))
		throw;

	if (this->d == 1)
		return QuadraticField(this->a * factor.a, this->a * factor.b, factor.d, true);
	else
		return QuadraticField(this->a * factor.a + this->b * factor.b * factor.d,
							  this->a * factor.b + this->b * factor.a, factor.d, true);
}

QuadraticField& QuadraticField::operator*=(const QuadraticField &factor)
{
	if (!IsSameSystem(*this, factor))
		throw;

	if (*this == Zero)
		return *this;
	else if (factor == Zero)
		return *this = Zero;

	if (this->d == 1)
	{
		if (factor.d == 1)
		{
			this->a *= factor.a;
			return *this;
		}
		else
		{
			this->b = this->a * factor.b;
			this->a *= factor.a;
			this->d = factor.d;
			return *this;
		}
	}
	else if (factor.d == 1)
	{
		this->a *= factor.a;
		this->b *= factor.a;
		return *this;
	}
	else
		return *this = QuadraticField(this->a * factor.a + this->b * factor.b * factor.d,
									  this->a * factor.b + this->b * factor.a, factor.d, true);
}

const QuadraticField QuadraticField::operator/(const QuadraticField &divisor) const
{
	if (!IsSameSystem(*this, divisor))
		throw;

	return *this * Inverse(divisor);
}

QuadraticField& QuadraticField::operator/=(const QuadraticField &divisor)
{
	if (!IsSameSystem(*this, divisor))
		throw;

	return *this *= Inverse(divisor);
}
